#include "Tree.h"
#include "../../drawing.h"
#include "../../common.h"
#include "../../shaderManager.h"
#include "../../textureManager.h"
#include "../../log.h"
#include "../../twister.h"
#include "../../spline.h"
#include <math.h>

#include <lightimage.h>

namespace Tmpl {

// Tree Stem
struct TreeRenderPoint
{
	float vOffset;
	float time;
};
class TreeStem
{
public:
	typedef std::vector<TreeStem*> TreeStemArray;

	TreeStem(TreeStem* parent, const TreeBranchSettings& settings, Twister* twister, Orientationf o, float size, float parentSplinePos = 0.0f);
	~TreeStem();

	void AddTreePolies(VertexArray& vertices, IndicesArray& indices);
	void AddLeavesPolies(Twister* twister, VertexArray& vertices, IndicesArray& indices);
	void AddLeaf(const Matrix4x4f& mat, const Vec2f& leafSize, VertexArray& vertices, IndicesArray& indices);

	TreeStem* mParent;
	std::vector<TreeRenderPoint> mRenderPoints;

	float mSize;
	float mParentSplinePos;
	TreeStemArray mChilds;
	const TreeBranchSettings& mSettings;
	SplineOf mSpline;
};
TreeStem::TreeStem(TreeStem* parent, const TreeBranchSettings& settings, Twister* twister, Orientationf o, float size, float parentSplinePos)
			: mSettings(settings)
{
	mSize = size;
	mParentSplinePos = parentSplinePos;
	mParent = parent;

	// first step is to create the slices
	float length = settings.axisLength + settings.axisLength * settings.axisVariation * (twister->GetRandom()-twister->GetRandom());
	float stepDist = length / float(settings.axisResolution);

	// inherit form parent
	Orientationf target = o;
	if (mParent)
	{
		stepDist *= mParent->mSettings.branchingOffsetInfluence.GetPointAtX(mParentSplinePos).y;
		o = mParent->mSpline.GetPointAtTime(mParentSplinePos);
	}

	mSpline.SetType(SplineOf::SPLINE_BEZIER_QUAD);
	mSpline.AddPoint(o, false);
	o.rot = Slerp(o.rot, target.rot, 0.5f);
	o.rot.Normalize();
	o.pos += o.rot.GetMatrix().Transform(Vec3f(0,1,0)) * stepDist;
	mSpline.AddPoint(o, false);
	for (int i=1; i<settings.axisResolution; ++i)
	{
		float stepValue = float(i+1)/float(settings.axisResolution);

		// Get the rotation
		Vec3f angles = o.rot.GetRotation();

		// add the bending
		angles.x += settings.axisBending * (twister->GetRandom()-twister->GetRandom()) * 180.0f;
		angles.z += settings.axisBending * (twister->GetRandom()-twister->GetRandom()) * 180.0f;

		// light influence, tries to reduce the angles back to 0 (upwards)
		float y = settings.axisLightInfluence.GetPointAtTime(stepValue).y;
		angles -= angles * y * 0.5f;

		// Set new rotation
		o.rot.SetRotation(angles);

		// update towards target
		o.rot = Slerp(o.rot, target.rot, 0.3f);
		o.rot.Normalize();

		// update position
		Vec3f v = o.rot.GetMatrix().Transform(Vec3f(0,1,0));
		o.pos += v * stepDist;

		// add the new orientation
		mSpline.AddPoint(o, false);
	}
	mSpline.EvaluateLength();

	// Do we have to branch the stem?
	float childSize;
	for (unsigned int i=0; i<settings.childs.size(); ++i)
	{
		const TreeBranchSettings& childSettings = settings.childs[i];

		// branch count
		int branchCount = settings.branchingBranches;

		// range
		float min = settings.branchingOffsetMin;
		float max = settings.branchingOffsetMax;
		float range = (max-min);
		float dist = range / float(branchCount);

		// rotation
		float rotStep = 360.0f / float(branchCount);

		for (int b=0; b<branchCount; ++b)
		{
			// Calculate length and rotation
			float variation = 0.0f;
			if (b > 0) variation = dist * twister->GetRandom() * settings.branchingAxisVariation;
			float t = max - (b * dist + variation) * range;
			if (settings.branchingInheritRadius) 
				childSize = mSize * settings.renderProfile.GetPointAtX(t).y;
			else
				childSize = childSettings.renderRadius;
			if (childSize < 0.001f) continue;

			// Calculate Orientation
			Orientationf orientation = mSpline.GetPointAtTime(t);
			Vec3f rot = orientation.GetRotation();
			rot.x += 90.0f;
			rot.z += float(b) * rotStep + rotStep * (twister->GetRandom()-twister->GetRandom()) * 0.25f;
			orientation.rot.SetRotation(rot);

			// Create the branch
			TreeStem* child = new TreeStem(this, childSettings, twister, orientation, childSize, t);
			mChilds.push_back(child);
		}
	}
}
TreeStem::~TreeStem()
{
	for (TreeStemArray::iterator it=mChilds.begin(); it!=mChilds.end(); ++it)
	{
		delete *it;
	}
}
void TreeStem::AddTreePolies(VertexArray& vertices, IndicesArray& indices)
{
	int segments = mSettings.renderSegments;
	if (segments >= 3)
	{
		int slices = mSettings.renderResolution;
		Vertex vt;
		int offset = (int)vertices.size();

		float delta = 2.0f * PI / float(segments);
		float u = 10.0f / float(segments);
		float step = 1.0f / float(slices);
		Vec2f uvOffset; 

		Orientationf o;
		Matrix4x4f mat;
		float size;
		float lastV = 0.0f;

		// Build Vertices
		for (int y=0; y<=slices; ++y)
		{
			float t = float(y)*step;

			// update matrix
			if (y==0 && mParent && mSettings.branchingInheritRadius)
			{
				// Inherit the parent position
				int parentRenderPoint = 0;
				for (int i=0; i<(int)mParent->mRenderPoints.size(); ++i)
				{
					if (mParent->mRenderPoints[i].time > mParentSplinePos) break;
					parentRenderPoint = i;
				}
				size = mParent->mSize * mParent->mSettings.renderProfile.GetPointAtX(mParent->mRenderPoints[parentRenderPoint].time).y;
				o = mParent->mSpline.GetPointAtTime(mParent->mRenderPoints[parentRenderPoint].time);

				// update uv
				lastV = mParent->mRenderPoints[parentRenderPoint].vOffset;
			}
			else
			{
				Vec3f lastPos = o.pos;

				size = mSize * mSettings.renderProfile.GetPointAtX(t).y;
				o = mSpline.GetPointAtTime(t);

				// update uv
				lastV += (lastPos - o.pos).Length();
			}
			mat = o.rot.Normalized().GetMatrix();

			// add this render point
			TreeRenderPoint trp;
			trp.time = t;
			trp.vOffset = lastV;
			mRenderPoints.push_back(trp);

			//Log::Print("t: %f, Vec %f %f %f Rot %f %f %f", t, o.pos.x, o.pos.y, o.pos.z, o.rot.GetRotation().x, o.rot.GetRotation().y, o.rot.GetRotation().z);

			// add the vertices
			float rot = 0.0f;
			size += 0.001f;
			for (int x=0; x<=segments; ++x)
			{
				// create the offset
				Vec3f tpos = mat.Transform(Vec3f(size * cosf(rot), 0.0f, size * sinf(rot)));
				
				// position
				vt.pos = o.pos + tpos;

				// normal
				vt.normal = tpos.Normalized();

				// calculate uv
				vt.uv = Vec2f(float(x) * u, lastV);

				// add vertex
				vertices.push_back(vt);

				// next
				rot += delta;
			}
		}

		// Add indices
		segments = mSettings.renderSegments + 1;
		for (int y=0; y<slices; ++y)
		{
			for (int x=0; x<segments-1; ++x)
			{
				indices.push_back(offset + ((y  )*segments+x  ));
				indices.push_back(offset + ((y  )*segments+x+1));
				indices.push_back(offset + ((y+1)*segments+x+1));
				indices.push_back(offset + ((y+1)*segments+x  ));
			}
		}
	}

	// add child branches
	for (TreeStemArray::iterator it=mChilds.begin(); it!=mChilds.end(); ++it)
	{
		TreeStem* stem = *it;
		stem->AddTreePolies(vertices, indices);
	}
}

void TreeStem::AddLeaf(const Matrix4x4f& mat, const Vec2f& leafSize, VertexArray& vertices, IndicesArray& indices)
{
	Vertex vt;
	vt.normal = mat.Transform(Vec3f(0,1,0)).Normalized();

	vt.uv = Vec2f(0.0f, 0.0f);
	vt.pos = mat.Transform(Vec3f(-leafSize.x, 0.0f, -leafSize.y));
	vertices.push_back(vt);

	vt.uv = Vec2f(0.0f, 1.0f);
	vt.pos = mat.Transform(Vec3f(-leafSize.x, 0.0f,  leafSize.y));
	vertices.push_back(vt);

	vt.uv = Vec2f(1.0f, 1.0f);
	vt.pos = mat.Transform(Vec3f( leafSize.x, 0.0f,  leafSize.y));
	vertices.push_back(vt);

	vt.uv = Vec2f(1.0f, 0.0f);
	vt.pos = mat.Transform(Vec3f( leafSize.x, 0.0f, -leafSize.y));
	vertices.push_back(vt);
}
void TreeStem::AddLeavesPolies(Twister* twister, VertexArray& vertices, IndicesArray& indices)
{
	int slices = 100;
	float step = 1.0f / float(slices);

	// Generate the leaves
	Matrix4x4f oMat, tmp, leaf;
	Vec3f tpos;
	if (mChilds.size() == 0)
	for (int y=0; y<=slices; ++y)
	{
		float t = float(y)*step;
		if (twister->GetRandom() < 0.9f) continue;

		// tree size
		float size = mSize * mSettings.renderProfile.GetPointAtX(t).y - 0.1f;

		// leaf size
		Vec2f leafSize = mSettings.leafSize * (0.5f + twister->GetRandom());

		// update matrix
		Orientationf o = mSpline.GetPointAtTime(t);
		Vec3f oPos = o.pos;
		//oMat = o.rot.GetMatrix();

		// Recreate the matrix to make the up vector face the sky
		float r = twister->GetRandom()-twister->GetRandom();
		Vec3f oRot = o.rot.GetRotation();
		oRot.x += twister->GetRandomNegative() * 90.0f;
		oRot.z += twister->GetRandomNegative() * 90.0f;
		oMat.SetRotation(oRot);

		// First Leaf
		leaf.Identity();
		tpos = oMat.Transform(Vec3f((size+leafSize.x) * cosf(1.0f*PI), 0.0f, (size+leafSize.y) * sinf(1.0f*PI)));
		leaf.Translate(oPos + tpos);
		leaf.Concatenate(oMat);
		tmp.SetRotateX(90.0f + twister->GetRandomNegative() * 30.0f);
		leaf.Concatenate(tmp);
		tmp.SetRotateY(90.0f);
		leaf.Concatenate(tmp);
		AddLeaf(leaf, leafSize, vertices, indices);
		/*
		tmp.SetRotateX(90.0f);
		leaf.Concatenate(tmp);
		AddLeaf(leaf, leafSize, vertices, indices);
		*/

		// second leaf
		leaf.Identity();
		tpos = oMat.Transform(Vec3f((size+leafSize.x) * cosf(0), 0.0f, (size+leafSize.y) * sinf(0)));
		leaf.Translate(oPos + tpos);
		leaf.Concatenate(oMat);
		tmp.SetRotateX(90.0f + twister->GetRandomNegative() * 30.0f);
		leaf.Concatenate(tmp);
		tmp.SetRotateY(-90.0f);
		leaf.Concatenate(tmp);
		AddLeaf(leaf, leafSize, vertices, indices);
		/*
		tmp.SetRotateX(90.0f);
		leaf.Concatenate(tmp);
		AddLeaf(leaf, leafSize, vertices, indices);
		*/
	}

	// add child branches
	for (TreeStemArray::iterator it=mChilds.begin(); it!=mChilds.end(); ++it)
	{
		(*it)->AddLeavesPolies(twister, vertices, indices);
	}
}

// Render Models
class TreeStemRender : public Model
{
public:
	TreeStemRender(ShaderProgram* shader, const char* trunkTexture) : Model(shader)
	{
		mRenderMode = GL_QUADS;
	}

	int GetTriangleCount(){return mIndicesCount / 2;}
protected:
	void BindSettings()
	{
		mShader->Uniform1i("tex", 0);		
		mShader->Uniform1i("texNormal", 1);	

		glActiveTexture(GL_TEXTURE1);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, TextureManager::GetTexture("data/Models/Trees/oakbark_normalmap.png").texId);

		glActiveTexture(GL_TEXTURE0);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, TextureManager::GetTexture("data/Models/Trees/oakbark.png").texId);
	}

	void UnbindSettings()
	{
		glActiveTexture(GL_TEXTURE1);
		glDisable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, 0);
	
		glActiveTexture(GL_TEXTURE0);
		glDisable(GL_TEXTURE_2D);
	}
};

class TreeLeavesRender : public Model
{
public:
	TreeLeavesRender(ShaderProgram* shader, const char* trunkTexture) : Model(shader)
	{
		mRenderMode = GL_QUADS;
	}

	int GetTriangleCount(){return mVertexCount / 2;}
protected:
	void BindSettings()
	{
		mShader->Uniform1i("tex", 0);

		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		glActiveTexture(GL_TEXTURE0);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, TextureManager::GetTexture("data/Models/Trees/leaf.png").texId);
	}

	void UnbindSettings()
	{
		glDisable(GL_BLEND);
		glActiveTexture(GL_TEXTURE0);
		glDisable(GL_TEXTURE_2D);
	}
};

// Base tree, used for rendering
Tree::Tree(const TreeSettings& settings)
{
	mSettings = settings;

	Twister* twister = new Twister();
	twister->Init(mSettings.seed, 1.0f);

	VertexArray vertices;
	IndicesArray indices;
	mStems = 0;
	mLeaves = 0;

	// Generate Tree
	TreeStem* tree = 0;
	if (mSettings.trunk.renderProfile.GetPointCount() > 0 && mSettings.trunk.axisLength > 0)
	{
		tree = new TreeStem(0, mSettings.trunk, twister, Orientationf(Vec3f(), Quaternionf()), mSettings.trunk.renderRadius);
	}

	// Build the treechunk model
	if (tree) tree->AddTreePolies(vertices, indices);
	if (indices.size() > 0)
	{
		mStems = new TreeStemRender(ShaderManager::LoadShader("Data/Tree"), "");
		mStems->BuildModel(vertices, indices);
	}
	vertices.clear();
	indices.clear();

	// Generate Leaves
	mLeaves = new TreeLeavesRender(ShaderManager::LoadShader("Data/TreeLeaves"), "");
	tree->AddLeavesPolies(twister, vertices, indices);
	if (vertices.size() > 0)
	{
		mLeaves->BuildModel(vertices, indices);
	}
	else
	{
		delete mLeaves;
		mLeaves = 0;
	}

	// clean up
	if (tree) delete tree;
	delete twister;
}

Tree::~Tree()
{
	delete mStems;
	delete mLeaves;
}

void Tree::Render(const Matrix4x4f& projection, const Matrix4x4f* modelView, int count)
{
	// Render the tree
	if (mStems) mStems->Render(projection, modelView, count);

	// Render the leaves
	if (mLeaves) mLeaves->Render(projection, modelView, count);
}
}